package cn.like.backEnd.auth;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.like.backEnd.entity.constants.AuthUserConstants;
import cn.like.base.auth.AuthContext;
import cn.like.base.support.exception.code.ExceptionCode;
import cn.like.base.support.result.Rest;
import cn.like.common.dto.LoginSuccessDto;
import cn.like.common.dto.SignInRequestDto;
import cn.like.entity.AuthUser;
import cn.like.feign.AuthUserFeignService;
import cn.like.util.BeanUtil;
import cn.like.util.RsaHelper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Objects;

/**
 * desc: auth 管理器 <br>
 * details:
 * <pre>
 *      1.登录
 *      2.为用户解锁
 *  </pre>
 *
 * @author like 980650920@qq.com
 * @date 2021-07-08 19:42:22
 */
@Component
public class AuthManager {

    // inject
    private final AuthUserFeignService authUserFeignService;
    private final RsaHelper rsaHelper;
    private final AuthContext<AuthUser> authContext;

    public AuthManager(AuthUserFeignService authUserFeignService, RsaHelper rsaHelper, AuthContext<AuthUser> authContext) {
        this.authUserFeignService = authUserFeignService;
        this.rsaHelper = rsaHelper;
        this.authContext = authContext;
    }

    /**
     * desc: 登录 <br>
     * <pre>
     *     登录用，并保存信息到{@link AuthContext}
     * </pre>
     *
     * @param account  账号
     * @param password 密码
     * @return: {@link Rest<?> } 成功返回用户信息以及token
     */
    public Rest<?> login(String account, String password) {
        // 1.检查用户密码是否正确，是否被锁定，以及是否需要解锁
        final Rest<AuthUser> check = this.check(account, password);
        if (!check.ok()) return check;
        final AuthUser user = check.serviceData();

        // 2.检查成功,根据用户信息生成token，并保存到baseContext中
        final String token = authContext.doLogin(user);

        return Rest.of(BeanUtil.copy(user, new LoginSuccessDto()).setToken(token));
    }

    /**
     * description: 注册
     *
     * @param signInRequestDto 登录请求dto
     * @return {@link Rest<?>}
     */
    public Rest<?> signUp(SignInRequestDto signInRequestDto) {
        // 1. 远程调用创建用户，如果account存在则创建失败
        final AuthUser user = authUserFeignService.createAuthUser(signInRequestDto).serviceData();

        // 2.创建成功，直接登录。
        final String token = authContext.doLogin(user);

        // todo 初始权限
        return Rest.of(BeanUtil.copy(user, new LoginSuccessDto())
                .setToken(token));
    }

    /**
     * desc: 解锁用户 <br>
     *
     * @param user 需要解锁的用户
     * @return:
     */
    public void unlockUser(AuthUser user) {
        // reset of password lock
        user.setPasswordErrorNum(0);
        user.setPasswordExpireTime(null);
        user.setStatus(AuthUserConstants.Status_Enabled);
        // 更新
        user.updateById();
    }

    /**
     * desc: 尝试锁定用户 <br>
     * details: 输错密码到第4次就上锁
     *
     * @param user 尝试锁定的用户
     * @return:
     */
    private void tryLockUser(AuthUser user) {
        final int passwordErrorNum = user.getPasswordErrorNum();

        // add password error count
        user.setPasswordErrorNum(passwordErrorNum + 1);

        // 密码输入错误次数大于3次，上锁，到第3次不锁。
        if (user.getPasswordErrorNum() > AuthUserConstants.passwordMaxErrorNum) {
            // 禁用
            user.setStatus(AuthUserConstants.Status_Disabled);
            // 解锁时间
            user.setPasswordExpireTime(LocalDateTimeUtil.offset(LocalDateTime.now(), 1, ChronoUnit.DAYS));
        }

        // 设置最后一次输错密码时间
        user.setPasswordErrorLastTime(LocalDateTime.now());

        // 更新
        user.updateById();
    }

    /**
     * desc:检查两次输入的密码是否相等
     *
     * @param password   密码
     * @param rePassword 重复密码
     * @return boolean true 不相等
     */
    public boolean checkIsNotEqPassword(String password, String rePassword) {
        return !StringUtils.equals(password, rePassword);
    }

    // ========================================   private methods

    /**
     * desc: 检查用户的密码是否正确，以及是否需要上锁，解锁等。 <br>
     *
     * @param account  账户
     * @param password 密码
     * @return: {@link Rest<AuthUser> }
     */
    private Rest<AuthUser> check(String account, String password) {
        final AuthUser user = authUserFeignService.findByAccount(account).serviceData();

        // 1.用户不存在
        if (Objects.isNull(user)) return Rest.failed(ExceptionCode.USER_IS_NOT_EXIST);

        // 2.比较密码
        if (!this.comparingPassword(password, user)) {

            // 3.密码错误，进入锁用户流程
            this.tryLockUser(user);

            return Rest.failed(ExceptionCode.PASSWORD_IS_NO_VALID);
        }

        // 4.账户被锁了，判断解锁
        if (this.isLock(user)) {
            // 比较锁定过期时间，还在锁定过程中
            if (!this.compareLockingExpirationTime(user)) return Rest.failed(ExceptionCode.LOCK);

            // 解除锁定
            unlockUser(user);
        }

        return Rest.of(user);
    }

    // true 是锁定状态
    public boolean isLock(AuthUser user) {
        return user.getStatus().equals(AuthUserConstants.Status_Disabled);
    }

    // true 可以解锁
    private boolean compareLockingExpirationTime(AuthUser user) {
        return LocalDateTimeUtil.now().isAfter(user.getPasswordExpireTime());
    }

    // true 密码输入正确
    private boolean comparingPassword(String password, AuthUser user) {
        return rsaHelper.decrypt(user.getPassword()).equals(password);
    }
}
